﻿using Avalonia.Media;
using CommunityToolkit.Mvvm.Input;
using PingInfo.avalonia.Helpers;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using CommunityToolkit.Mvvm.ComponentModel;
using static System.Text.RegularExpressions.Regex;
using Timer = System.Timers.Timer;
using PingInfo.avalonia.Models;


namespace PingInfo.avalonia.ViewModels
{
    public class MainWindowViewModel : ObservableObject
    {
        private readonly Stopwatch _stopwatch = new();
        private readonly Timer _timer;
        private static readonly CountdownEvent FailedCountdownEvent = new(0);
        private static readonly CountdownEvent SuccessCountdownEvent = new(0);

        #region Binding Parameters

        public int PingTimes
        {
            get => _pingTimes;
            set => SetProperty(ref _pingTimes, value);
        }

        public string IpInputStr
        {
            get => _ipInputStr;
            set => SetProperty(ref _ipInputStr, value);
        }

        public ObservableCollection<IpEntity> IpEntities
        {
            get => _ipEntities;
            set
            {
                SetProperty(ref _ipEntities, value);
                SetProperty(ref _btnPingEnabled, value.Count > 0, nameof(BtnPingEnabled));
                ProgressMax = IpEntities.Count;
            }
        }

        public bool Include1
        {
            get => _include1;
            set
            {
                SetProperty(ref _include1, value);
                UpdateIpEntitiesWithCurrentSettings();
            }
        }

        public bool Include255
        {
            get => _include255;
            set
            {
                SetProperty(ref _include255, value);
                UpdateIpEntitiesWithCurrentSettings();
            }
        }

        public string UsedTime
        {
            get => _usedTime;
            set => SetProperty(ref _usedTime, value);
        }

        public int ProgressMax
        {
            get => _progressMax;
            set => SetProperty(ref _progressMax, value);
        }

        public int ProgressValue
        {
            get => _progressValue;
            set
            {
                SetProperty(ref _progressValue, value);
                if (value == ProgressMax)
                {
                    if (!PingAgain)
                    {
                        BtnPingEnabled = true;
                        BtnAddEnabled = true;
                        BtnPingChecked = false;
                    }

                    _stopwatch.Stop();
                }

                var ts = _stopwatch.Elapsed;
                UsedTime = $"{ts.TotalSeconds:f3}";
            }
        }

        public bool BtnAddEnabled
        {
            get => _btnAddEnabled;
            set => SetProperty(ref _btnAddEnabled, value);
        }

        public bool BtnPingEnabled
        {
            get => _btnPingEnabled;
            set => SetProperty(ref _btnPingEnabled, value);
        }

        public bool BtnPingChecked
        {
            get => _btnPingChecked;
            set => SetProperty(ref _btnPingChecked, value);
        }

        public int ThreadCount
        {
            get => _threadCount;
            set => SetProperty(ref _threadCount, value);
        }


        public int EnableCount
        {
            get => _enableCount;
            set => SetProperty(ref _enableCount, value);
        }

        public int DisableCount
        {
            get => _disableCount;
            set => SetProperty(ref _disableCount, value);
        }


        public int Timeout
        {
            get => _timeout;
            set => SetProperty(ref _timeout, value);
        }

        public int PingSize
        {
            get => _pingSize;
            set
            {
                if (value < 0) value = 0;
                if (value > 65535) value = 65535;
                SetProperty(ref _pingSize, value);
            }
        }

        public bool PingAgain
        {
            get => _pingAgain;
            set => SetProperty(ref _pingAgain, value);
        }

        public int PingAgainInterval
        {
            get => _pingAgainInterval;
            set => SetProperty(ref _pingAgainInterval, value);
        }

        public int PingAgainTimes
        {
            get => _pingAgainTimes;
            set => SetProperty(ref _pingAgainTimes, value);
        }

        /// <summary>
        ///     快速PING 模式时，PING 次数
        /// </summary>
        private int _pingTimes = 1;

        /// <summary>
        ///     ip with Cidr
        /// </summary>
        private string _ipInputStr = "";

        /// <summary>
        ///     IP 列表
        /// </summary>
        private ObservableCollection<IpEntity> _ipEntities = [];

        /// <summary>
        ///     进度条总进度
        /// </summary>
        private int _progressMax;

        /// <summary>
        ///     进度条当前进度
        /// </summary>
        private int _progressValue;

        /// <summary>
        ///     PING 延时
        /// </summary>
        private int _timeout = 1;

        /// <summary>
        ///     PING 耗时
        /// </summary>
        private string _usedTime = "0";

        /// <summary>
        ///     添加时包含1
        /// </summary>
        private bool _include1;

        /// <summary>
        ///     添加时包含 255
        /// </summary>
        private bool _include255;

        /// <summary>
        ///     添加按钮可用状态
        /// </summary>
        private bool _btnAddEnabled = true;

        /// <summary>
        ///     PING 按钮可用状态
        /// </summary>
        private bool _btnPingEnabled;

        /// <summary>
        ///     PING 按钮选中状态
        /// </summary>
        private bool _btnPingChecked;

        /// <summary>
        ///     PING 线程数
        /// </summary>
        private int _threadCount = 128;

        /// <summary>
        ///     可达数量
        /// </summary>
        private int _enableCount;

        /// <summary>
        ///     不可达数量
        /// </summary>
        private int _disableCount;

        /// <summary>
        ///     Ping 包大小
        /// </summary>
        private int _pingSize = 32;

        /// <summary>
        ///     是否持续 PING
        /// </summary>
        private bool _pingAgain;

        /// <summary>
        ///     持续 Ping 间隔
        /// </summary>
        private int _pingAgainInterval = 5;

        /// <summary>
        ///     持续 PING 次数
        /// </summary>
        private int _pingAgainTimes;

        #endregion

        #region Binding Commands

        public RelayCommand<object> PingCommand => new(PingAction);
        public RelayCommand<object> AddCommand => new(AddAction);

        public RelayCommand<object> AboutCommand => new(AboutAction);

        //public RelayCommand<object> ScreenShotCommand => new(ScreenShotAction);
        // public RelayCommand<object> ExportCommand => new(ExportAction);

        #endregion

        #region Methods

        public MainWindowViewModel()
        {
            _timer = new Timer();
            _timer.Elapsed += HandleTimer;
        }

        private void AboutAction(object? obj)
        {
            // Process.Start(obj.ToString()!);
        }

        private void AddAction(object? obj)
        {
            if (string.IsNullOrEmpty(IpInputStr))
                return;

            if (IsMatch(IpInputStr, @"[^\d\.\-/]+"))
            {
                return;
            }

            if (IsMatch(IpInputStr, @"^(\d+\.){3}\d+(/\d{1,2}|-(\d+\.){3}\d+)?$"))
            {
                UpdateIpEntitiesWithCurrentSettings();
            }
        }

        private void DoPing(object? ipe)
        {
            if (ipe is not IpEntity ipEntity) return;
            Thread.CurrentThread.IsBackground = true;
            var pingHelper = new PingHelper();
            for (var i = 0; i < PingTimes; i++)
            {
                var isSuccess = pingHelper.SendPing(IPAddress.Parse(ipEntity.Ip!), Timeout * 1000, PingSize);
                if (isSuccess)
                {
                    var currentTime = pingHelper.LastReply.RoundTripTime;
                    var previousDelay = ipEntity.Delay;

                    ipEntity.Delay = previousDelay < 0
                        ? currentTime
                        : (previousDelay + currentTime) / 2;

                    ipEntity.Log +=
                        $"{i + 1}#: {currentTime}ms, ttl={pingHelper.LastReply.Ttl,3}{(i < PingTimes - 1 ? "\n" : "")}";
                }
                else
                {
                    ipEntity.Delay = -1;
                    ipEntity.Log += $"{i + 1}#: time out{(i < PingTimes - 1 ? "\n" : "")}";
                }
            }

            if (ipEntity.Delay != -1) SuccessCountdownEvent.Signal();
            else FailedCountdownEvent.Signal();
            EnableCount = IpEntities.Count - SuccessCountdownEvent.CurrentCount;
            DisableCount = IpEntities.Count - FailedCountdownEvent.CurrentCount;
            ProgressValue = IpEntities.Count * 2 - SuccessCountdownEvent.CurrentCount -
                            FailedCountdownEvent.CurrentCount;
        }

        //检查线程池的方法
        private void CheckThreadPool(object state, bool timeout)
        {
            ThreadPool.GetAvailableThreads(out var workerThreads, out var _);
            ThreadPool.GetMaxThreads(out var maxWordThreads, out var _);
            //当可用的线数与池程池最大的线程相等时表示线程池中所有的线程已经完成
            if (workerThreads != maxWordThreads)
                return;
            //当执行此方法后CheckThreadPool将不再执行
            _rhw!.Unregister(null);
            //此处加入所有线程完成后的处理代码
            ProgressValue = ProgressMax;
            EnableCount = IpEntities.Count - SuccessCountdownEvent.CurrentCount;
            DisableCount = IpEntities.Count - FailedCountdownEvent.CurrentCount;
        }

        private RegisteredWaitHandle? _rhw;

        private void PingAction(object? obj)
        {
            // 处理F5快捷键或按钮点击
            if (obj?.ToString() == "f5")
            {
                BtnPingChecked = !BtnPingChecked;
            }

            // 如果按钮未启用，直接返回
            if (!BtnPingEnabled) return;

            BtnAddEnabled = !BtnPingChecked;

            if (!BtnPingChecked)
            {
                _timer.Stop();
                return;
            }

            if (PingAgain)
            {
                PingAgainTimes = 0;
                _timer.Interval = Math.Max(1, PingAgainInterval) * 1000;
                _timer.Start();
            }

            HandleTimer(null, null);
        }

        private void HandleTimer(object? sender, ElapsedEventArgs? e)
        {
            ProgressValue = 0;
            EnableCount = 0;
            DisableCount = 0;
            _stopwatch.Restart();
            ThreadPool.SetMinThreads(ThreadCount, ThreadCount);
            ThreadPool.SetMaxThreads(ThreadCount * 2, ThreadCount * 2);
            SuccessCountdownEvent.Reset(IpEntities.Count);
            FailedCountdownEvent.Reset(IpEntities.Count);
            _rhw = ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), CheckThreadPool!, null, 100,
                false);
            Parallel.ForEach(IpEntities, (ipEntity, _, _) =>
            {
                ipEntity.Background = Brushes.Transparent;
                ipEntity.Log = string.Empty;
                ipEntity.Delay = -1;
                ThreadPool.QueueUserWorkItem(DoPing, ipEntity);
            });
            if (PingAgain)
            {
                PingAgainTimes++;
            }
        }


        /// <summary>
        ///     Generate IP Collection
        /// </summary>
        /// <param name="ipStart">IP AAddress Starting</param>
        /// <param name="ipEnd">IP Address Ending</param>
        /// <param name="isInclude1">Include 1</param>
        /// <param name="isInclude255">Include 255</param>
        /// <returns>ObservableCollection&lt;IpEntity></returns>
        private static ObservableCollection<IpEntity> GenerateIpList(string ipStart, string ipEnd, bool isInclude1,
            bool isInclude255)
        {
            var ipTemp = new ObservableCollection<IpEntity>();
            var iStartIp = IpToInt(ipStart);
            var iEndIp = IpToInt(ipEnd);
            if (iEndIp < iStartIp) return ipTemp;
            var maxWidth = 0.0;
            for (var ip = iStartIp; ip <= iEndIp; ip++)
            {
                var lastOctet = (byte)(ip & 0x000000FF);
                var shouldSkip = lastOctet switch
                {
                    0 => true,
                    1 => !isInclude1,
                    255 => !isInclude255,
                    _ => false
                };

                if (shouldSkip) continue;
                var ipstr = IntToIp(ip);
                maxWidth = Math.Max(maxWidth, ipstr.Length * 8.0);
                var ipe = new IpEntity
                {
                    Ip = ipstr,
                };
                ipTemp.Add(ipe);
            }

            foreach (var ip in ipTemp)
            {
                ip.Width = maxWidth;
            }

            return ipTemp;
        }

        /// <summary>
        ///     Ip Address String to Integer
        /// </summary>
        /// <param name="ipStr">Ip Address</param>
        /// <returns>Integer</returns>
        private static uint IpToInt(string ipStr)
        {
            var ip = ipStr.Split('.');
            var ipCode = 0xFFFFFF00 | byte.Parse(ip[3]);
            ipCode = ipCode & 0xFFFF00FF | uint.Parse(ip[2]) << 0x8;
            ipCode = ipCode & 0xFF00FFFF | uint.Parse(ip[1]) << 0x10;
            ipCode = ipCode & 0x00FFFFFF | uint.Parse(ip[0]) << 0x18;
            return ipCode;
        }

        /// <summary>
        ///     Ip Address Integer to String
        /// </summary>
        /// <param name="ipCode">Ip Address Integer</param>
        /// <returns>String</returns>
        private static string IntToIp(uint ipCode)
        {
            var a = (byte)((ipCode & 0xFF000000) >> 0x18);
            var b = (byte)((ipCode & 0x00FF0000) >> 0x10);
            var c = (byte)((ipCode & 0x0000FF00) >> 0x8);
            var d = (byte)(ipCode & 0x000000FF);
            var ipStr = $"{a}.{b}.{c}.{d}";
            return ipStr;
        }

        /// <summary>
        ///     Calculate Available Ip Address
        /// </summary>
        /// <param name="cidr">Cidr</param>
        /// <returns></returns>
        private static uint CalculateAvailableHosts(int cidr)
        {
            return uint.Parse(Math.Pow(2, 32 - cidr).ToString(CultureInfo.CurrentCulture));
        }

        private const string CidrPattern = @"^(\d+\.){3}\d+/\d{1,2}$";
        private const string IpRangePattern = @"^(\d+\.){3}\d+-(\d+\.){3}\d+$";
        private const string SingleIpPattern = @"^(\d+\.){3}\d+$";

        private void UpdateIpEntitiesWithCurrentSettings()
        {
            if (string.IsNullOrWhiteSpace(IpInputStr))
                return;

            try
            {
                if (IsMatch(IpInputStr, CidrPattern))
                {
                    ProcessCidrFormat(IpInputStr);
                }
                else if (IsMatch(IpInputStr, IpRangePattern))
                {
                    ProcessIpRangeFormat(IpInputStr);
                }
                else if (IsMatch(IpInputStr, SingleIpPattern))
                {
                    IpEntities = GenerateIpList(IpInputStr, IpInputStr, Include1, Include255);
                }
            }
            catch (Exception ex)
            {
                // 适当的错误处理
                Debug.WriteLine($"IP处理错误: {ex.Message}");
            }
        }

        private void ProcessCidrFormat(string ipInput)
        {
            var ipCidr = ipInput.Split('/');
            var ipStart = ipCidr[0];
            var cidr = int.Parse(ipCidr[1]);

            if (cidr is > 30 or < 16)
                return;

            var ipParts = ipStart.Split('.');
            var lastBlockIp = double.Parse(ipParts[3]);
            var availableHosts = CalculateAvailableHosts(cidr);
            var broadcast = (uint)Math.Ceiling(lastBlockIp / availableHosts) * availableHosts;

            var ipEnd = IntToIp(IpToInt(ipStart) - uint.Parse(ipParts[3]) + broadcast);
            IpEntities = GenerateIpList(ipStart, ipEnd, Include1, Include255);
        }

        private void ProcessIpRangeFormat(string ipInput)
        {
            var ipRange = ipInput.Split('-');
            IpEntities = GenerateIpList(ipRange[0], ipRange[1], Include1, Include255);
        }

        ~MainWindowViewModel()
        {
            _timer.Dispose();
        }

        #endregion
    }
}